/** @file   display.c
    @author M. P. Hayes, UCECE,H. Cumming
    @date   21 August 2007
    @brief  Dot matrix display module.  
*/

/*
TODO:
-variable scroll rate?
-fix const's
-LOTS of optimisation, tidying
-changed display_update for supa dupa efficiency. Thoughts?
*/


//#include <string.h>
#include "config.h"
#include "display.h"
#include "font.h"

#define PORTD_D			(1<<DDB0)|(1<<DDB1)|(1<<DDB2)
#define PORTC_D			(1<<DDC2)|(1<<DDC3)
#define ENABLE_ALL_ROWS	0xFF
#define EMPTY_COLUMN	0xFF
#define SCROLL_SPEED	10		//how many times per second to scroll
#define MAX_STRLEN		20
#define PORTC_MASK		0x0C	//00001100  This should be in terms of the defines in 
#define PORTB_MASK		0x07	//00000111	target.h
typedef enum  {DISPLAY_OFF,DISPLAY_STR, DISPLAY_NUM} display_mode_t;

/*Constants*/
static const uint8_t cPORTB[] ={(1<<PB0),(1<<PB1),(1<<PB2),0,0};
static const uint8_t cPORTC[]={0,0,0,(1<<PC2),(1<<PC3)};

/* Static Variables*/
static display_mode_t display_mode;
static uint16_t update_rate;
static char display_string[MAX_STRLEN];
static uint8_t display_number = 0;
static bool string_reset = FALSE;

/*Prototypes*/
static void copyArray(const char * a, char * b);
static uint8_t length(const char *str);
static inline uint8_t swapNibbles(uint8_t byte);
static void displayColumn(uint8_t columndata, uint8_t column);

/** Display string.
	@param *string string to display
*/
void display_puts (const char *string)
{
	copyArray(string, display_string);
	display_mode = DISPLAY_STR;
	string_reset = TRUE;//tell display update to reset
}

/* *
	Display a 2 digit integer. ASSUMES a two digit integer 
	@param number the 2 digit number 
*/
void display_puti (uint8_t number)
{
	display_number = number;
	display_mode = DISPLAY_NUM;
}

/** 
	Update (refresh and scroll) display.  
*/
void display_update (void)
{
	static uint8_t columns[5] = {0, 0, 0, 0, 0};
	static uint8_t curChar = 0;		//where we are in the string
	static int8_t curCol = 0;		//where we are at in the scroll
	static uint8_t tick = 0;		//which columns we are on	
	static uint8_t scroll_rate = 0;	//how often we should scroll
	
	switch(display_mode) {
		case DISPLAY_STR: {
			scroll_rate++;
			if (string_reset == TRUE && curCol == 0 && curChar == 0){//reset everything
				curChar = curCol = scroll_rate = 0;
				for (uint8_t i=curCol;i<5;i++){						
					columns[i] = asciitable[(display_string[curChar] -32)*5 + i];
				}
				string_reset = FALSE;
			}
			if (scroll_rate == update_rate / SCROLL_SPEED){//to scroll or not to scroll
				scroll_rate = 0;
				if (++curCol >= LMATRIX_COLS){	/*go to next character*/
					curCol = -1;
					if (display_string[++curChar] == 0) {curChar = 0;} /*back to start*/
				}
				for (uint8_t i=0;i<4;i++){//shift columns left
					columns[i] = columns[i+1];
				}
				//work out last column
				switch (curCol){
					case -1: case 0:
						columns[4] = asciitable[(display_string[curChar] -32)*5 + 4 + curCol];
						break;
					case 1:
						columns[4] = EMPTY_COLUMN;
						break;
					default: 
					{
						uint8_t tmp;
						if (*(display_string + (tmp = curChar + 1)) == '\0') tmp = 0;
						columns[4] = asciitable[(display_string[tmp] -32)*5 + curCol - 2];
					}
				}
			}
			//most of the time its just this that runs
			displayColumn(columns[tick],tick);
		}
		break;
		case DISPLAY_NUM:{
			uint8_t firstByte = sidewaysFont[(display_number/10)*5 + tick];	//Top Number
			uint8_t secondByte = sidewaysFont[(display_number%10)*5 + tick];//Bottom Number
			uint8_t data = firstByte + swapNibbles(secondByte) - 0xFF;		//Add 'em up to get column data
			displayColumn(data,tick);
		}
		case DISPLAY_OFF:
		default:	break;		
	}	
	if (++tick == 5) {tick = 0;}
}

/** 
    Initialise display.
    @param update_rate the rate \c display_update is called  
*/
void display_init (uint16_t rate)
{
	update_rate = rate;
	display_mode = DISPLAY_OFF;
	DDRB = PORTD_D;  		//port b direction bits
	DDRC = PORTC_D;			//port c direction bits
	DDRD = ENABLE_ALL_ROWS;	//port d direction bits
	PORTD = 0xFF;			//so nothing displays at start
}

/** Returns the length of the null terminated string 'str'
	@param *str a pointer to the string
*/
static uint8_t length(const char *str)
{	
	uint8_t i = 0;
	while(str[i++]);
	return i;
}

/** Copies an array and adds a space on the end
	@param a the source array
	@param b the destination array, must be bigger than a
*/
static void copyArray(const char *a, char *b)
{
	uint8_t len = (length(a)<MAX_STRLEN) ? length(a) : MAX_STRLEN;
	uint8_t i = 0;
	do {b[i]=a[i];} while(i++<len);
	b[len-1]=' ';
	b[len]='\0';
	
}

/** Swaps the nibbles in a byte
	@param byte the byte to do the nibble swapping with
	@return the nibble swapped byte
*/
static inline uint8_t swapNibbles(uint8_t byte)
{
	return (byte<<4)|(byte>>4);
}

/** Outputs a column of data to the display device
	@param columndata the data for the column
	@param column which column to output the data to
*/
static void displayColumn(uint8_t columndata, uint8_t column)
{
	PORTD = 0xFF; //Prevents 'shadowing'
	PORTB = (PORTB | PORTB_MASK) & ~cPORTB[column];
	PORTC = (PORTC | PORTC_MASK) & ~cPORTC[column];
	PORTD = columndata;
}

